/***************************************************************************
# Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#  * Neither the name of NVIDIA CORPORATION nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
***************************************************************************/
#pragma once
#include "MeshLightData.h"

namespace Falcor
{
    /** Class that holds a collection of mesh lights for a scene.

        Each mesh light is represented by a mesh instance with an emissive material.

        This class has utility functions for updating and pre-processing the mesh lights.
        The LightCollection can be used standalone, but more commonly it will be wrapped
        by an emissive light sampler.
    */
    class dlldecl LightCollection : public std::enable_shared_from_this<LightCollection>
    {
    public:
        using SharedPtr = std::shared_ptr<LightCollection>;
        using SharedConstPtr = std::shared_ptr<const LightCollection>;

        static const uint32_t kInvalidIndex = -1;

        enum class UpdateFlags : uint32_t
        {
            None                = 0u,   ///< Nothing was changed.
            MatrixChanged       = 1u,   ///< Mesh instance transform changed.
            AnimationChanged    = 2u,   ///< Vertices changed due to animation.
        };

        struct UpdateStatus
        {
            std::vector<UpdateFlags> lightsUpdateInfo;
        };

        struct MeshLightStats
        {
            // Stats before pre-processing (input data).
            uint32_t meshLightCount = 0;                ///< Number of mesh lights.
            uint32_t triangleCount = 0;                 ///< Number of mesh light triangles (total).
            uint32_t meshesTextured = 0;                ///< Number of mesh lights with textured emissive.
            uint32_t trianglesTextured = 0;             ///< Number of mesh light triangles with textured emissive.

            // Stats after pre-processing (what's used during rendering).
            uint32_t trianglesCulled = 0;               ///< Number of triangles culled (due to zero radiance and/or area).
            uint32_t trianglesActive = 0;               ///< Number of active (non-culled) triangles.
            uint32_t trianglesActiveUniform = 0;        ///< Number of active triangles with const radiance.
            uint32_t trianglesActiveTextured = 0;       ///< Number of active triangles with textured radiance.
        };

        /** Represents one mesh light triangle vertex.
        */
        struct MeshLightVertex
        {
            glm::vec3 pos;      ///< World-space position.
            glm::vec2 uv;       ///< Texture coordinates in emissive texture (if textured).
        };

        /** Represents one mesh light triangle.
        */
        struct MeshLightTriangle
        {
            // TODO: Perf of indexed vs non-indexed on GPU. We avoid level of indirection, but have more bandwidth non-indexed.
            MeshLightVertex vtx[3];                             ///< Vertices. These are non-indexed for now.
            uint32_t        lightIdx = kInvalidIndex;           ///< Per-triangle index into mesh lights array.

            // Pre-computed quantities.
            glm::vec3       normal = glm::vec3(0);              ///< Triangle's face normal in world space.
            glm::vec3       averageRadiance = glm::vec3(0);     ///< Average radiance emitted over triangle. For textured emissive the radiance varies over the surface.
            float           luminousFlux = 0.f;                 ///< Pre-integrated luminous flux (lumens) emitted per side of the triangle for double-sided emitters (total flux is 2x this value).
            float           area = 0.f;                         ///< Triangle area in world space units.

            /** Returns the center of the triangle in world space.
            */
            glm::vec3 getCenter() const
            {
                return (vtx[0].pos + vtx[1].pos + vtx[2].pos) / 3.0f;
            }
        };


        virtual ~LightCollection() = default;

        /** Creates a light collection for the given scene.
            Note that update() must be called before the collection is ready to use.
            \param[in] pRenderContext The render context.
            \param[in] pScene The scene.
            \return Ptr to the created object, or nullptr if an error occured.
        */
        static SharedPtr create(RenderContext* pRenderContext, const Scene::SharedPtr& pScene);

        /** Updates the light collection to the current state of the scene.
            \param[in] pRenderContext The render context.
            \param[out] pUpdateStatus Stores information about which type of updates were performed for each mesh light. This is an optional output parameter.
            \return True if the lighting in the scene has changed since the last frame.
        */
        bool update(RenderContext* pRenderContext, UpdateStatus* pUpdateStatus = nullptr);

        /** Render the GUI.
            \return True if options that may affect the rendering have changed.
        */
        virtual bool renderUI(Gui::Widgets& widgets);

        /** Specialize a program to use this light collection.
            Note that ProgramVars may need to be re-created after this call, check the return value.
            \param[in] pProgram The Program to which macro definitions will be added.
            \return True if the ProgramVars needs to be re-created.
        */
        bool prepareProgram(ProgramBase* pProgram) const;

        /** Bind the light collection data to a program vars object.
            The default implementation calls setIntoBlockCommon().
            Note that prepareProgram() must have been called before this function.
            \param[in] pVars The program vars to set the data into.
            \param[in] pCB The constant buffer to set the data into.
            \param[in] varName The name of the data variable in the constant buffer.
            \return True if successful, false otherwise.
        */
        bool setIntoProgramVars(ProgramVars* pVars, const ConstantBuffer::SharedPtr& pCB, const std::string& varName) const { return setIntoBlockCommon(pVars->getDefaultBlock(), pCB, varName); }

        /** Bind the light collection data to a parameter block object.
            The default implementation calls setIntoBlockCommon().
            Note that prepareProgram() must have been called before this function.
            \param[in] pBlock The parameter block to set the data into.
            \param[in] varName The name of the data variable in the parameter block.
            \return True if successful, false otherwise.
        */
        bool setIntoParameterBlock(const ParameterBlock::SharedPtr& pBlock, const std::string& varName) const { return setIntoBlockCommon(pBlock, pBlock->getDefaultConstantBuffer(), varName); }

        /** Bind the light collection data to a given constant buffer in a parameter block.
            Note that prepareProgram() must have been called before this function.
            \param[in] pBlock The parameter block to set the data into (possibly the default parameter block).
            \param[in] pCB The constant buffer in the parameter block to set the data into.
            \param[in] varName The name of the data variable.
            \return True if successful, false otherwise.
        */
        bool setIntoBlockCommon(const ParameterBlock::SharedPtr& pBlock, const ConstantBuffer::SharedPtr& pCB, const std::string& varName) const;

        /** Returns the total number of active (non-culled) triangle lights.
        */
        uint32_t getActiveLightCount() const { return getStats().trianglesActive; }

        /** Returns the total number of triangle lights (may include culled triangles).
        */
        uint32_t getTotalLightCount() const { return mTriangleCount; }

        /** Returns stats.
        */
        const MeshLightStats& getStats() const { computeStats(); return mMeshLightStats; }

        /** Returns a CPU buffer with all emissive triangles in world space.
            Note that update() must have been called before for the data to be valid.
            Call prepareSyncCPUData() ahead of time to avoid stalling the GPU.
        */
        const std::vector<MeshLightTriangle>& getMeshLightTriangles() const { syncCPUData(); return mMeshLightTriangles; }

        /** Returns a CPU buffer with all mesh lights.
            Note that update() must have been called before for the data to be valid.
        */
        const std::vector<MeshLightData>& getMeshLights() const { return mMeshLights; }

        /** Prepare for syncing the CPU data.
            If the mesh light triangles will be accessed with getMeshLightTriangles()
            performance can be improved by calling this function ahead of time.
            This function schedules the copies so that it can be read back without delay later.
        */
        void prepareSyncCPUData(RenderContext* pRenderContext) const { copyDataToStagingBuffer(pRenderContext); }

        // Internal update flags. This only public for enum_class_operators() to work.
        enum class CPUOutOfDateFlags : uint32_t
        {
            None = 0u,
            Positions = 1u << 0,
            TexCoords = 1u << 1,
            TriangleData = 1u << 2,
            All = (1u << 3) - 1
        };

    protected:
        LightCollection() = default;

        bool init(RenderContext* pRenderContext, const Scene::SharedPtr& pScene);
        bool initIntegrator();
        bool setupMeshLights();
        void build(RenderContext* pRenderContext);
        void prepareTriangleData(RenderContext* pRenderContext);
        void prepareMeshData();
        void integrateEmissive(RenderContext* pRenderContext);
        void computeStats() const;
        void buildTriangleList(RenderContext* pRenderContext);
        void updateTrianglePositions(RenderContext* pRenderContext, const std::vector<uint32_t>& updatedLights);

        void copyDataToStagingBuffer(RenderContext* pRenderContext) const;
        void syncCPUData() const;

        // Internal state
        Scene::SharedPtr                        mpScene;

        std::vector<MeshLightData>              mMeshLights;            ///< List of all mesh lights.
        uint32_t                                mTriangleCount = 0;     ///< Total number of triangles in all mesh lights (= mMeshLightTriangles.size()). This may include culled triangles.

        mutable std::vector<MeshLightTriangle>  mMeshLightTriangles;    ///< List of all pre-processed mesh light triangles.
        mutable MeshLightStats                  mMeshLightStats;        ///< Stats before/after pre-processing of mesh lights. Do not access this directly, use getStats() which ensures the stats are up-to-date.
        mutable bool                            mStatsValid = false;    ///< True when stats are valid.

        // GPU resources for the mesh light vertex/triangle data.
        // TODO: Perf of individual buffers vs StructuredBuffer<vertex>? 
        // We should profile. The code would be simpler with StructuredBuffer.
        Buffer::SharedPtr                       mpMeshLightsVertexPos;  ///< Vertex positions in world space for all mesh light triangles (3 * mTriangleCount elements).
        Buffer::SharedPtr                       mpMeshLightsTexCoords;  ///< Texture coordinates for all mesh light triangles (3 * mTriangleCount elements).
        StructuredBuffer::SharedPtr             mpTriangleData;         ///< Per-triangle data for emissive triangles (mTriangleCount elements).
        StructuredBuffer::SharedPtr             mpMeshData;             ///< Per-mesh data for emissive meshes (mMeshLights.size() elements).
        TypedBuffer<uint32_t>::SharedPtr        mpPerMeshInstanceOffset; ///< Per-mesh instance offset into emissive triangles array (Scene::getMeshInstanceCount() elements).

        mutable Buffer::SharedPtr               mpStagingBuffer;        ///< Staging buffer used for retrieving the vertex positions, texture coordinates and light IDs from the GPU.
        GpuFence::SharedPtr                     mpStagingFence;         ///< Fence used for waiting on the staging buffer being filled in.

        Sampler::SharedPtr                      mpSamplerState;         ///< Material sampler for emissive textures.

        // Shader programs.
        struct
        {
            GraphicsProgram::SharedPtr          pProgram;
            GraphicsVars::SharedPtr             pVars;
            GraphicsState::SharedPtr            pState;
            Sampler::SharedPtr                  pPointSampler;      ///< Point sampler for fetching individual texels in integrator. Must use same wrap mode etc. as material sampler.
            Buffer::SharedPtr                   pResultBuffer;      ///< The output of the integration pass is written here. Using raw buffer for fp32 compatibility.
        } mIntegrator;

        ComputePass::SharedPtr                  mpTriangleListBuilder;
        ComputePass::SharedPtr                  mpTrianglePositionUpdater;
        ComputePass::SharedPtr                  mpFinalizeIntegration;

        mutable CPUOutOfDateFlags               mCPUInvalidData = CPUOutOfDateFlags::None;  ///< Flags indicating which CPU data is valid.
        mutable bool                            mStagingBufferValid = true;                 ///< Flag to indicate if the contents of the staging buffer is up-to-date.
    };

    enum_class_operators(LightCollection::CPUOutOfDateFlags);
    enum_class_operators(LightCollection::UpdateFlags);
}
